// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

package com.android.tools.r8.ir.optimize.lambda.kotlin;

import com.android.tools.r8.code.Const16;
import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.function.IntFunction;

// Represents a k-style lambda group created to combine several lambda classes
// generated by kotlin compiler for regular kotlin lambda expressions, like:
//
//      -----------------------------------------------------------------------
//      fun foo(m: String, v: Int): () -> String {
//        val lambda: (String, Int) -> String = { s, i -> s.substring(i) }
//        return { "$m: $v" }
//      }
//      -----------------------------------------------------------------------
//
// Regular stateless k-style lambda class structure looks like below:
// NOTE: stateless k-style lambdas do not always have INSTANCE field.
//
// -----------------------------------------------------------------------------------------------
// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function2<
//                          Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;>;
// final class lambdas/LambdasKt$foo$lambda1$1
//                  extends kotlin/jvm/internal/Lambda
//                  implements kotlin/jvm/functions/Function2  {
//
//     public synthetic bridge invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
//
//     public final invoke(Ljava/lang/String;I)Ljava/lang/String;
//       @Lorg/jetbrains/annotations/NotNull;() // invisible
//         @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
//
//     <init>()V
//
//     public final static Llambdas/LambdasKt$foo$lambda1$1; INSTANCE
//
//     static <clinit>()V
//
//     OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
//     final static INNERCLASS lambdas/LambdasKt$foo$lambda1$1 null null
// }
// -----------------------------------------------------------------------------------------------
//
// Regular stateful k-style lambda class structure looks like below:
//
// -----------------------------------------------------------------------------------------------
// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Ljava/lang/String;>;
// final class lambdas/LambdasKt$foo$1
//                  extends kotlin/jvm/internal/Lambda
//                  implements kotlin/jvm/functions/Function0  {
//
//     public synthetic bridge invoke()Ljava/lang/Object;
//
//     public final invoke()Ljava/lang/String;
//       @Lorg/jetbrains/annotations/NotNull;() // invisible
//
//     <init>(Ljava/lang/String;I)V
//
//     final synthetic Ljava/lang/String; $m
//     final synthetic I $v
//
//     OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
//     final static INNERCLASS lambdas/LambdasKt$foo$1 null null
// }
// -----------------------------------------------------------------------------------------------
//
// Key k-style lambda class details:
//   - extends kotlin.jvm.internal.Lambda
//   - implements one of kotlin.jvm.functions.Function0..Function22, or FunctionN
//     see: https://github.com/JetBrains/kotlin/blob/master/libraries/
//                  stdlib/jvm/runtime/kotlin/jvm/functions/Functions.kt
//     and: https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md
//   - lambda class is created as an anonymous inner class
//   - lambda class carries generic signature and kotlin metadata attribute
//   - class instance fields represent captured values and have an instance constructor
//     with matching parameters initializing them (see the second class above)
//   - stateless lambda *may* be  implemented as a singleton with a static field storing the
//     only instance and initialized in static class constructor (see the first class above)
//   - main lambda method usually matches an exact lambda signature and may have
//     generic signature attribute and nullability parameter annotations
//   - optional bridge method created to satisfy interface implementation and
//     forwarding call to lambda main method
//
final class KStyleLambdaGroup extends KotlinLambdaGroup {
  private KStyleLambdaGroup(GroupId id) {
    super(id);
  }

  @Override
  protected ClassBuilder getBuilder(DexItemFactory factory) {
    return new ClassBuilder(factory, "kotlin-style lambda group");
  }

  @Override
  public ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
      Kotlin kotlin, AppInfoWithSubtyping appInfo) {
    return new ClassValidator(kotlin, appInfo);
  }

  @Override
  protected String getGroupSuffix() {
    return "ks$";
  }

  // Specialized group id.
  final static class GroupId extends KotlinLambdaGroupId {
    GroupId(String capture, DexType iface,
        String pkg, String signature, DexEncodedMethod mainMethod,
        InnerClassAttribute inner, EnclosingMethodAttribute enclosing) {
      super(capture, iface, pkg, signature, mainMethod, inner, enclosing);
    }

    @Override
    public boolean equals(Object obj) {
      return obj instanceof GroupId && computeEquals((KotlinLambdaGroupId) obj);
    }

    @Override
    String getLambdaKindDescriptor() {
      return "Kotlin k-style lambda group";
    }

    @Override
    public LambdaGroup createGroup() {
      return new KStyleLambdaGroup(this);
    }
  }

  // Specialized class validator.
  private final class ClassValidator extends KotlinLambdaClassValidator {
    ClassValidator(Kotlin kotlin, AppInfoWithSubtyping appInfo) {
      super(kotlin, KStyleLambdaGroup.this, appInfo);
    }

    @Override
    int getInstanceInitializerSize(List<DexEncodedField> captures) {
      return captures.size() + 3;
    }

    @Override
    int validateInstanceInitializerEpilogue(
        com.android.tools.r8.code.Instruction[] instructions, int index)
        throws LambdaStructureError {
      if (!(instructions[index] instanceof Const4) &&
          !(instructions[index] instanceof Const16)) {
        throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
      }
      index++;
      if (!(instructions[index] instanceof com.android.tools.r8.code.InvokeDirect
              || instructions[index] instanceof com.android.tools.r8.code.InvokeDirectRange)
          || instructions[index].getMethod() != kotlin.functional.lambdaInitializerMethod) {
        throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
      }
      index++;
      if (!(instructions[index] instanceof ReturnVoid)) {
        throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
      }
      return index + 1;
    }
  }

  // Specialized class builder.
  private final class ClassBuilder extends KotlinLambdaGroupClassBuilder<KStyleLambdaGroup> {
    ClassBuilder(DexItemFactory factory, String origin) {
      super(KStyleLambdaGroup.this, factory, origin);
    }

    @Override
    protected DexType getSuperClassType() {
      return factory.kotlin.functional.lambdaType;
    }

    @Override
    SyntheticSourceCode createInstanceInitializerSourceCode(
        DexType groupClassType, DexMethod initializerMethod, Position callerPosition) {
      return new InstanceInitializerSourceCode(
          factory,
          groupClassType,
          group.getLambdaIdField(factory),
          id -> group.getCaptureField(factory, id),
          initializerMethod,
          factory.kotlin.functional.getArity(id.iface),
          callerPosition);
    }
  }

  // Specialized instance initializer code.
  private final static class InstanceInitializerSourceCode
      extends KotlinInstanceInitializerSourceCode {
    private final int arity;
    private final DexMethod lambdaInitializer;

    InstanceInitializerSourceCode(
        DexItemFactory factory,
        DexType lambdaGroupType,
        DexField idField,
        IntFunction<DexField> fieldGenerator,
        DexMethod method,
        int arity,
        Position callerPosition) {
      super(lambdaGroupType, idField, fieldGenerator, method, callerPosition);
      this.arity = arity;
      this.lambdaInitializer = factory.createMethod(factory.kotlin.functional.lambdaType,
          factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
    }

    @Override
    void prepareSuperConstructorCall(int receiverRegister) {
      int arityRegister = nextRegister(ValueType.INT);
      add(builder -> builder.addConst(TypeLatticeElement.INT, arityRegister, arity));
      add(
          builder ->
              builder.addInvoke(
                  Type.DIRECT,
                  lambdaInitializer,
                  lambdaInitializer.proto,
                  Lists.newArrayList(ValueType.OBJECT, ValueType.INT),
                  Lists.newArrayList(receiverRegister, arityRegister),
                  false /* isInterface */));
    }
  }
}
